/*
 * Copyright (C) 2004, 2005, 2006 Joe Walnes.
 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2018 XStream Committers.
 * All rights reserved.
 *
 * The software in this package is published under the terms of the BSD
 * style license a copy of which has been included with this distribution in
 * the LICENSE.txt file.
 *
 * Created on 02. March 2006 by Joerg Schaible
 */
package com.feilong.lib.xstream.converters.reflection;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.feilong.lib.xstream.converters.ConversionException;
import com.feilong.lib.xstream.converters.Converter;
import com.feilong.lib.xstream.converters.MarshallingContext;
import com.feilong.lib.xstream.converters.SingleValueConverter;
import com.feilong.lib.xstream.converters.UnmarshallingContext;
import com.feilong.lib.xstream.core.Caching;
import com.feilong.lib.xstream.core.ReferencingMarshallingContext;
import com.feilong.lib.xstream.core.util.ArrayIterator;
import com.feilong.lib.xstream.core.util.FastField;
import com.feilong.lib.xstream.core.util.Fields;
import com.feilong.lib.xstream.core.util.HierarchicalStreams;
import com.feilong.lib.xstream.core.util.Primitives;
import com.feilong.lib.xstream.core.util.SerializationMembers;
import com.feilong.lib.xstream.io.ExtendedHierarchicalStreamWriterHelper;
import com.feilong.lib.xstream.io.HierarchicalStreamReader;
import com.feilong.lib.xstream.io.HierarchicalStreamWriter;
import com.feilong.lib.xstream.mapper.CannotResolveClassException;
import com.feilong.lib.xstream.mapper.Mapper;

public abstract class AbstractReflectionConverter implements Converter,Caching{

    protected final ReflectionProvider             reflectionProvider;

    protected final Mapper                         mapper;

    /**
     * @deprecated As of 1.4.8, use {@link #serializationMembers}.
     */
    @Deprecated
    protected transient SerializationMethodInvoker serializationMethodInvoker;

    protected transient SerializationMembers       serializationMembers;

    private transient ReflectionProvider           pureJavaReflectionProvider;

    public AbstractReflectionConverter(Mapper mapper, ReflectionProvider reflectionProvider){
        this.mapper = mapper;
        this.reflectionProvider = reflectionProvider;
        serializationMethodInvoker = new SerializationMethodInvoker();
        serializationMembers = serializationMethodInvoker.serializationMembers;
    }

    protected boolean canAccess(Class type){
        try{
            reflectionProvider.getFieldOrNull(type, "%");
            return true;
        }catch (NoClassDefFoundError e){
            // restricted type in GAE
        }
        return false;
    }

    @Override
    public void marshal(Object original,final HierarchicalStreamWriter writer,final MarshallingContext context){
        final Object source = serializationMembers.callWriteReplace(original);

        if (source != original && context instanceof ReferencingMarshallingContext){
            ((ReferencingMarshallingContext) context).replace(original, source);
        }
        if (source.getClass() != original.getClass()){
            String attributeName = mapper.aliasForSystemAttribute("resolves-to");
            if (attributeName != null){
                writer.addAttribute(attributeName, mapper.serializedClass(source.getClass()));
            }
            context.convertAnother(source);
        }else{
            doMarshal(source, writer, context);
        }
    }

    protected void doMarshal(final Object source,final HierarchicalStreamWriter writer,final MarshallingContext context){
        final List fields = new ArrayList();
        final Map defaultFieldDefinition = new HashMap();
        final Class sourceType = source.getClass();

        // Attributes might be preferred to child elements ...
        reflectionProvider.visitSerializableFields(source, new ReflectionProvider.Visitor(){

            final Set writtenAttributes = new HashSet();

            @Override
            public void visit(String fieldName,Class type,Class definedIn,Object value){
                if (!mapper.shouldSerializeMember(definedIn, fieldName)){
                    return;
                }
                if (!defaultFieldDefinition.containsKey(fieldName)){
                    Class lookupType = source.getClass();
                    // See XSTR-457 and OmitFieldsTest
                    if (definedIn != sourceType && !mapper.shouldSerializeMember(lookupType, fieldName)){
                        lookupType = definedIn;
                    }
                    defaultFieldDefinition.put(fieldName, reflectionProvider.getField(lookupType, fieldName));
                }

                SingleValueConverter converter = mapper.getConverterFromItemType(fieldName, type, definedIn);
                if (converter != null){
                    final String attribute = mapper.aliasForAttribute(mapper.serializedMember(definedIn, fieldName));
                    if (value != null){
                        if (writtenAttributes.contains(fieldName)){
                            ConversionException exception = new ConversionException(
                                            "Cannot write field as attribute for object, attribute name already in use");
                            exception.add("field-name", fieldName);
                            exception.add("object-type", sourceType.getName());
                            throw exception;
                        }
                        final String str = converter.toString(value);
                        if (str != null){
                            writer.addAttribute(attribute, str);
                        }
                    }
                    writtenAttributes.add(fieldName);
                }else{
                    fields.add(new FieldInfo(fieldName, type, definedIn, value));
                }
            }
        });

        final FieldMarshaller fieldMarshaller = new FieldMarshaller(){

            @Override
            public void writeField(String fieldName,String aliasName,Class<?> fieldType,Class<?> definedIn,Object newObj){
                Class actualType = newObj != null ? newObj.getClass() : fieldType;
                ExtendedHierarchicalStreamWriterHelper.startNode(
                                writer,
                                aliasName != null ? aliasName : mapper.serializedMember(sourceType, fieldName),
                                actualType);

                if (newObj != null){
                    Class defaultType = mapper.defaultImplementationOf(fieldType);
                    if (!actualType.equals(defaultType)){
                        String serializedClassName = mapper.serializedClass(actualType);
                        if (!serializedClassName.equals(mapper.serializedClass(defaultType))){
                            String attributeName = mapper.aliasForSystemAttribute("class");
                            if (attributeName != null){
                                writer.addAttribute(attributeName, serializedClassName);
                            }
                        }
                    }

                    final Field defaultField = (Field) defaultFieldDefinition.get(fieldName);
                    if (defaultField.getDeclaringClass() != definedIn){
                        String attributeName = mapper.aliasForSystemAttribute("defined-in");
                        if (attributeName != null){
                            writer.addAttribute(attributeName, mapper.serializedClass(definedIn));
                        }
                    }

                    Field field = reflectionProvider.getField(definedIn, fieldName);
                    marshallField(context, newObj, field);
                }
                writer.endNode();
            }

            @Override
            public void writeItem(Object item){
                if (item == null){
                    String name = mapper.serializedClass(null);
                    ExtendedHierarchicalStreamWriterHelper.startNode(writer, name, Mapper.Null.class);
                    writer.endNode();
                }else{
                    String name = mapper.serializedClass(item.getClass());
                    ExtendedHierarchicalStreamWriterHelper.startNode(writer, name, item.getClass());
                    context.convertAnother(item);
                    writer.endNode();
                }
            }
        };

        final Map hiddenMappers = new HashMap();
        for (Iterator fieldIter = fields.iterator(); fieldIter.hasNext();){
            FieldInfo info = (FieldInfo) fieldIter.next();
            if (info.value != null){
                final Field defaultField = (Field) defaultFieldDefinition.get(info.fieldName);
                Mapper.ImplicitCollectionMapping mapping = mapper.getImplicitCollectionDefForFieldName(
                                defaultField.getDeclaringClass() == info.definedIn ? sourceType : info.definedIn,
                                info.fieldName);
                if (mapping != null){
                    Set mappings = (Set) hiddenMappers.get(info.fieldName);
                    if (mappings == null){
                        mappings = new HashSet();
                        mappings.add(mapping);
                        hiddenMappers.put(info.fieldName, mappings);
                    }else{
                        if (!mappings.add(mapping)){
                            mapping = null;
                        }
                    }
                }
                if (mapping != null){
                    if (context instanceof ReferencingMarshallingContext){
                        if (info.value != Collections.EMPTY_LIST && info.value != Collections.EMPTY_SET
                                        && info.value != Collections.EMPTY_MAP){
                            ReferencingMarshallingContext refContext = (ReferencingMarshallingContext) context;
                            refContext.registerImplicit(info.value);
                        }
                    }
                    final boolean isCollection = info.value instanceof Collection;
                    final boolean isMap = info.value instanceof Map;
                    final boolean isEntry = isMap && mapping.getKeyFieldName() == null;
                    final boolean isArray = info.value.getClass().isArray();
                    for (Iterator iter = isArray ? new ArrayIterator(info.value)
                                    : isCollection ? ((Collection) info.value).iterator()
                                                    : isEntry ? ((Map) info.value).entrySet().iterator()
                                                                    : ((Map) info.value).values().iterator(); iter.hasNext();){
                        Object obj = iter.next();
                        final String itemName;
                        final Class itemType;
                        if (obj == null){
                            itemType = Object.class;
                            itemName = mapper.serializedClass(null);
                        }else if (isEntry){
                            final String entryName = mapping.getItemFieldName() != null ? mapping.getItemFieldName()
                                            : mapper.serializedClass(Map.Entry.class);
                            Map.Entry entry = (Map.Entry) obj;
                            ExtendedHierarchicalStreamWriterHelper.startNode(writer, entryName, entry.getClass());
                            fieldMarshaller.writeItem(entry.getKey());
                            fieldMarshaller.writeItem(entry.getValue());
                            writer.endNode();
                            continue;
                        }else if (mapping.getItemFieldName() != null){
                            itemType = mapping.getItemType();
                            itemName = mapping.getItemFieldName();
                        }else{
                            itemType = obj.getClass();
                            itemName = mapper.serializedClass(itemType);
                        }
                        fieldMarshaller.writeField(info.fieldName, itemName, itemType, info.definedIn, obj);
                    }
                }else{
                    fieldMarshaller.writeField(info.fieldName, null, info.type, info.definedIn, info.value);
                }
            }
        }
    }

    protected void marshallField(final MarshallingContext context,Object newObj,Field field){
        context.convertAnother(newObj, mapper.getLocalConverter(field.getDeclaringClass(), field.getName()));
    }

    @Override
    public Object unmarshal(final HierarchicalStreamReader reader,final UnmarshallingContext context){
        Object result = instantiateNewInstance(reader, context);
        result = doUnmarshal(result, reader, context);
        return serializationMembers.callReadResolve(result);
    }

    public Object doUnmarshal(final Object result,final HierarchicalStreamReader reader,final UnmarshallingContext context){
        final Class resultType = result.getClass();
        final Set seenFields = new HashSet(){

            /**
             * 
             */
            private static final long serialVersionUID = -2760462656944390472L;

            @Override
            public boolean add(Object e){
                if (!super.add(e)){
                    throw new DuplicateFieldException(((FastField) e).getName());
                }
                return true;
            }
        };

        // process attributes before recursing into child elements.
        Iterator it = reader.getAttributeNames();
        while (it.hasNext()){
            String attrAlias = (String) it.next();
            // TODO: realMember should return FastField
            String attrName = mapper.realMember(resultType, mapper.attributeForAlias(attrAlias));
            Field field = reflectionProvider.getFieldOrNull(resultType, attrName);
            if (field != null && shouldUnmarshalField(field)){
                Class classDefiningField = field.getDeclaringClass();
                if (!mapper.shouldSerializeMember(classDefiningField, attrName)){
                    continue;
                }

                // we need a converter that produces a string representation only
                SingleValueConverter converter = mapper.getConverterFromAttribute(classDefiningField, attrName, field.getType());
                Class type = field.getType();
                if (converter != null){
                    Object value = converter.fromString(reader.getAttribute(attrAlias));
                    if (type.isPrimitive()){
                        type = Primitives.box(type);
                    }
                    if (value != null && !type.isAssignableFrom(value.getClass())){
                        ConversionException exception = new ConversionException("Cannot convert type");
                        exception.add("source-type", value.getClass().getName());
                        exception.add("target-type", type.getName());
                        throw exception;
                    }
                    seenFields.add(new FastField(classDefiningField, attrName));
                    reflectionProvider.writeField(result, attrName, value, classDefiningField);
                }
            }
        }

        Map implicitCollectionsForCurrentObject = null;
        while (reader.hasMoreChildren()){
            reader.moveDown();

            String originalNodeName = reader.getNodeName();
            Class explicitDeclaringClass = readDeclaringClass(reader);
            Class fieldDeclaringClass = explicitDeclaringClass == null ? resultType : explicitDeclaringClass;
            String fieldName = mapper.realMember(fieldDeclaringClass, originalNodeName);
            Mapper.ImplicitCollectionMapping implicitCollectionMapping = mapper
                            .getImplicitCollectionDefForFieldName(fieldDeclaringClass, fieldName);
            final Object value;
            String implicitFieldName = null;
            Field field = null;
            Class type = null;
            if (implicitCollectionMapping == null){
                // no item of an implicit collection for this name ... do we have a field?
                field = reflectionProvider.getFieldOrNull(fieldDeclaringClass, fieldName);
                if (field == null){
                    // it is not a field ... do we have a field alias?
                    Class itemType = mapper.getItemTypeForItemFieldName(fieldDeclaringClass, fieldName);
                    if (itemType != null){
                        String classAttribute = HierarchicalStreams.readClassAttribute(reader, mapper);
                        if (classAttribute != null){
                            type = mapper.realClass(classAttribute);
                        }else{
                            type = itemType;
                        }
                    }else{
                        // it is not an alias ... do we have an element of an implicit
                        // collection based on type only?
                        try{
                            type = mapper.realClass(originalNodeName);
                            implicitFieldName = mapper.getFieldNameForItemTypeAndName(fieldDeclaringClass, type, originalNodeName);
                        }catch (CannotResolveClassException e){
                            // type stays null ...
                        }
                        if (type == null || (type != null && implicitFieldName == null)){
                            // either not a type or element is a type alias, but does not
                            // belong to an implicit field
                            handleUnknownField(explicitDeclaringClass, fieldName, fieldDeclaringClass, originalNodeName);

                            // element is unknown in declaring class, ignore it now
                            type = null;
                        }
                    }
                    if (type == null){
                        // no type, no value
                        value = null;
                    }else{
                        if (Map.Entry.class.equals(type)){
                            // it is an element of an implicit map with two elements now for
                            // key and value 
                            reader.moveDown();
                            final Object key = context.convertAnother(result, HierarchicalStreams.readClassType(reader, mapper));
                            reader.moveUp();
                            reader.moveDown();
                            final Object v = context.convertAnother(result, HierarchicalStreams.readClassType(reader, mapper));
                            reader.moveUp();
                            value = Collections.singletonMap(key, v).entrySet().iterator().next();
                        }else{
                            // recurse info hierarchy
                            value = context.convertAnother(result, type);
                        }
                    }
                }else{
                    boolean fieldAlreadyChecked = false;

                    // we have a field, but do we have to address a hidden one?
                    if (explicitDeclaringClass == null){
                        while (field != null && !(fieldAlreadyChecked = shouldUnmarshalField(field)
                                        && mapper.shouldSerializeMember(field.getDeclaringClass(), fieldName))){
                            field = reflectionProvider.getFieldOrNull(field.getDeclaringClass().getSuperclass(), fieldName);
                        }
                    }
                    if (field != null && (fieldAlreadyChecked || (shouldUnmarshalField(field)
                                    && mapper.shouldSerializeMember(field.getDeclaringClass(), fieldName)))){
                        String classAttribute = HierarchicalStreams.readClassAttribute(reader, mapper);
                        if (classAttribute != null){
                            type = mapper.realClass(classAttribute);
                        }else{
                            type = mapper.defaultImplementationOf(field.getType());
                        }
                        // TODO the reflection provider should already return the proper field
                        value = unmarshallField(context, result, type, field);
                        Class definedType = field.getType();
                        if (!definedType.isPrimitive()){
                            type = definedType;
                        }
                    }else{
                        value = null;
                    }
                }
            }else{
                // we have an implicit collection with defined names
                implicitFieldName = implicitCollectionMapping.getFieldName();
                type = implicitCollectionMapping.getItemType();
                if (type == null){
                    String classAttribute = HierarchicalStreams.readClassAttribute(reader, mapper);
                    type = mapper.realClass(classAttribute != null ? classAttribute : originalNodeName);
                }
                value = context.convertAnother(result, type);
            }

            if (value != null && !type.isAssignableFrom(value.getClass())){
                throw new ConversionException("Cannot convert type " + value.getClass().getName() + " to type " + type.getName());
            }

            if (field != null){
                reflectionProvider.writeField(result, fieldName, value, field.getDeclaringClass());
                seenFields.add(new FastField(field.getDeclaringClass(), fieldName));
            }else if (type != null){
                if (implicitFieldName == null){
                    // look for implicit field
                    implicitFieldName = mapper.getFieldNameForItemTypeAndName(
                                    fieldDeclaringClass,
                                    value != null ? value.getClass() : Mapper.Null.class,
                                    originalNodeName);
                }
                if (implicitCollectionsForCurrentObject == null){
                    implicitCollectionsForCurrentObject = new HashMap();
                }
                writeValueToImplicitCollection(
                                value,
                                implicitCollectionsForCurrentObject,
                                result,
                                new FieldLocation(implicitFieldName, fieldDeclaringClass));
            }

            reader.moveUp();
        }

        if (implicitCollectionsForCurrentObject != null){
            for (Iterator iter = implicitCollectionsForCurrentObject.entrySet().iterator(); iter.hasNext();){
                Map.Entry entry = (Map.Entry) iter.next();
                Object value = entry.getValue();
                if (value instanceof ArraysList){
                    Object array = ((ArraysList) value).toPhysicalArray();
                    final FieldLocation fieldLocation = (FieldLocation) entry.getKey();
                    final Field field = reflectionProvider.getFieldOrNull(fieldLocation.definedIn, fieldLocation.fieldName);
                    reflectionProvider.writeField(result, fieldLocation.fieldName, array, field != null ? field.getDeclaringClass() : null);
                }
            }
        }

        return result;
    }

    protected Object unmarshallField(final UnmarshallingContext context,final Object result,Class type,Field field){
        return context.convertAnother(result, type, mapper.getLocalConverter(field.getDeclaringClass(), field.getName()));
    }

    protected boolean shouldUnmarshalTransientFields(){
        return false;
    }

    protected boolean shouldUnmarshalField(Field field){
        return !(Modifier.isTransient(field.getModifiers()) && !shouldUnmarshalTransientFields());
    }

    private void handleUnknownField(Class classDefiningField,String fieldName,Class resultType,String originalNodeName){
        if (classDefiningField == null){
            for (Class cls = resultType; cls != null; cls = cls.getSuperclass()){
                if (!mapper.shouldSerializeMember(cls, originalNodeName)){
                    return;
                }
            }
        }
        throw new UnknownFieldException(resultType.getName(), fieldName);
    }

    private void writeValueToImplicitCollection(Object value,Map implicitCollections,Object result,final FieldLocation fieldLocation){
        Collection collection = (Collection) implicitCollections.get(fieldLocation);
        if (collection == null){
            final Field field = reflectionProvider.getFieldOrNull(fieldLocation.definedIn, fieldLocation.fieldName);
            Class physicalFieldType = field != null ? field.getType()
                            : reflectionProvider.getFieldType(result, fieldLocation.fieldName, null);
            if (physicalFieldType.isArray()){
                collection = new ArraysList(physicalFieldType);
            }else{
                Class fieldType = mapper.defaultImplementationOf(physicalFieldType);
                if (!(Collection.class.isAssignableFrom(fieldType) || Map.class.isAssignableFrom(fieldType))){
                    ObjectAccessException oaex = new ObjectAccessException(
                                    "Field is configured for an implicit Collection or Map, but is of an incompatible type");
                    oaex.add("field", result.getClass().getName() + "." + fieldLocation.fieldName);
                    oaex.add("field-type", fieldType.getName());
                    throw oaex;
                }
                if (pureJavaReflectionProvider == null){
                    pureJavaReflectionProvider = new PureJavaReflectionProvider();
                }
                Object instance = pureJavaReflectionProvider.newInstance(fieldType);
                if (instance instanceof Collection){
                    collection = (Collection) instance;
                }else{
                    Mapper.ImplicitCollectionMapping implicitCollectionMapping = mapper
                                    .getImplicitCollectionDefForFieldName(fieldLocation.definedIn, fieldLocation.fieldName);
                    collection = new MappingList((Map) instance, implicitCollectionMapping.getKeyFieldName());
                }
                reflectionProvider.writeField(result, fieldLocation.fieldName, instance, field != null ? field.getDeclaringClass() : null);
            }
            implicitCollections.put(fieldLocation, collection);
        }
        collection.add(value);
    }

    private Class readDeclaringClass(HierarchicalStreamReader reader){
        String attributeName = mapper.aliasForSystemAttribute("defined-in");
        String definedIn = attributeName == null ? null : reader.getAttribute(attributeName);
        return definedIn == null ? null : mapper.realClass(definedIn);
    }

    protected Object instantiateNewInstance(HierarchicalStreamReader reader,UnmarshallingContext context){
        String attributeName = mapper.aliasForSystemAttribute("resolves-to");
        String readResolveValue = attributeName == null ? null : reader.getAttribute(attributeName);
        Object currentObject = context.currentObject();
        if (currentObject != null){
            return currentObject;
        }else if (readResolveValue != null){
            return reflectionProvider.newInstance(mapper.realClass(readResolveValue));
        }else{
            return reflectionProvider.newInstance(context.getRequiredType());
        }
    }

    @Override
    public void flushCache(){
        serializationMethodInvoker.flushCache();
    }

    protected Object readResolve(){
        serializationMethodInvoker = new SerializationMethodInvoker();
        serializationMembers = serializationMethodInvoker.serializationMembers;
        return this;
    }

    public static class DuplicateFieldException extends ConversionException{

        /**
         * 
         */
        private static final long serialVersionUID = 4001152602743628195L;

        public DuplicateFieldException(String msg){
            super("Duplicate field " + msg);
            add("field", msg);
        }
    }

    public static class UnknownFieldException extends ConversionException{

        /**
         * 
         */
        private static final long serialVersionUID = 8343312024265219010L;

        public UnknownFieldException(String type, String field){
            super("No such field " + type + "." + field);
            add("field", field);
        }
    }

    private static class FieldLocation{

        final String fieldName;

        final Class  definedIn;

        FieldLocation(final String fieldName, final Class definedIn){
            this.fieldName = fieldName;
            this.definedIn = definedIn;
        }

        @Override
        public int hashCode(){
            final int prime = 7;
            int result = 1;
            result = prime * result + (definedIn == null ? 0 : definedIn.getName().hashCode());
            result = prime * result + (fieldName == null ? 0 : fieldName.hashCode());
            return result;
        }

        @Override
        public boolean equals(final Object obj){
            if (this == obj){
                return true;
            }
            if (obj == null){
                return false;
            }
            if (getClass() != obj.getClass()){
                return false;
            }
            final FieldLocation other = (FieldLocation) obj;
            if (definedIn != other.definedIn){
                return false;
            }
            if (fieldName == null){
                if (other.fieldName != null){
                    return false;
                }
            }else if (!fieldName.equals(other.fieldName)){
                return false;
            }
            return true;
        }
    }

    private static class FieldInfo extends FieldLocation{

        final Class  type;

        final Object value;

        FieldInfo(final String fieldName, final Class type, final Class definedIn, final Object value){
            super(fieldName, definedIn);
            this.type = type;
            this.value = value;
        }
    }

    private interface FieldMarshaller{

        void writeItem(final Object item);

        void writeField(
                        final String fieldName,
                        final String aliasName,
                        final Class<?> fieldType,
                        final Class<?> definedIn,
                        final Object newObj);
    }

    private static class ArraysList extends ArrayList{

        /**
         * 
         */
        private static final long serialVersionUID = -7645472918513821321L;

        final Class               physicalFieldType;

        ArraysList(Class physicalFieldType){
            this.physicalFieldType = physicalFieldType;
        }

        Object toPhysicalArray(){
            Object[] objects = toArray();
            Object array = Array.newInstance(physicalFieldType.getComponentType(), objects.length);
            if (physicalFieldType.getComponentType().isPrimitive()){
                for (int i = 0; i < objects.length; ++i){
                    Array.set(array, i, Array.get(objects, i));
                }
            }else{
                System.arraycopy(objects, 0, array, 0, objects.length);
            }
            return array;
        }
    }

    private class MappingList extends AbstractList{

        private final Map    map;

        private final String keyFieldName;

        private final Map    fieldCache = new HashMap();

        public MappingList(Map map, String keyFieldName){
            this.map = map;
            this.keyFieldName = keyFieldName;
        }

        @Override
        public boolean add(Object object){
            if (object == null){
                boolean containsNull = !map.containsKey(null);
                map.put(null, null);
                return containsNull;
            }
            Class itemType = object.getClass();

            if (keyFieldName != null){
                Field field = (Field) fieldCache.get(itemType);
                if (field == null){
                    field = reflectionProvider.getField(itemType, keyFieldName);
                    fieldCache.put(itemType, field);
                }
                if (field != null){
                    Object key = Fields.read(field, object);
                    return map.put(key, object) == null;
                }
            }else if (object instanceof Map.Entry){
                final Map.Entry entry = (Map.Entry) object;
                return map.put(entry.getKey(), entry.getValue()) == null;
            }

            ConversionException exception = new ConversionException("Element  is not defined as entry for implicit map");
            exception.add("map-type", map.getClass().getName());
            exception.add("element-type", object.getClass().getName());
            throw exception;
        }

        @Override
        public Object get(int index){
            throw new UnsupportedOperationException();
        }

        @Override
        public int size(){
            return map.size();
        }
    }
}
